"""
HTTP/REST connector implementation
"""

from __future__ import annotations

import base64
from datetime import datetime
from typing import Any

import httpx

from app.connectors.base import BaseConnector, ConnectionStatus, DataPoint, DataQuality
from app.connectors.exceptions import ConnectionError, ReadError, WriteError


class HTTPConnector(BaseConnector):
    """
    HTTP/REST connector for smart devices with REST APIs

    Configuration example:
    {
        "device_id": 1,
        "name": "Smart Meter",
        "base_url": "http://192.168.1.100:8080",
        "auth": {
            "type": "bearer",  # bearer / basic / api_key
            "token": "your_token_here"
        },
        "endpoints": {
            "temperature": "/api/sensors/temperature",
            "pressure": "/api/sensors/pressure"
        },
        "timeout": 10,
        "verify_ssl": false
    }

    Address formats:
    - Simple: "temperature" (lookup in endpoints config)
    - Full path: "/api/sensors/temperature"
    - With params: "/api/sensors/temperature?unit=celsius"
    """

    def __init__(self, config: dict[str, Any]) -> None:
        super().__init__(config)

        # Validate HTTP-specific required config
        if "base_url" not in config:
            raise ValueError("Missing required config keys: base_url")

        self.base_url: str = config["base_url"].rstrip("/")
        self.auth: dict[str, Any] = config.get("auth", {})
        self.endpoints: dict[str, str] = config.get("endpoints", {})
        self.verify_ssl: bool = config.get("verify_ssl", True)
        self.headers: dict[str, str] = config.get("headers", {})
        value_path = config.get("value_path")
        if isinstance(value_path, str):
            self.value_path: list[str] | None = [part for part in value_path.split(".") if part]
        elif isinstance(value_path, list):
            self.value_path = [str(part) for part in value_path]
        else:
            self.value_path = None

        # HTTP client
        self._client: httpx.AsyncClient | None = None

    async def connect(self) -> bool:
        """Establish connection to HTTP device (create HTTP client)"""
        try:
            self.status = ConnectionStatus.CONNECTING

            # Build authentication headers
            auth_headers = self._build_auth_headers()
            all_headers = {**self.headers, **auth_headers}

            # Create HTTP client
            self._client = httpx.AsyncClient(
                base_url=self.base_url, headers=all_headers, timeout=self.timeout, verify=self.verify_ssl
            )

            # Health check
            health_endpoint = self.config.get("health_endpoint", "/health")
            try:
                response = await self._client.get(health_endpoint)
                if response.status_code >= 500:
                    raise ConnectionError(f"Server error: {response.status_code}")
            except httpx.RequestError:
                pass  # Health endpoint may not exist, doesn't affect connection

            self.status = ConnectionStatus.CONNECTED
            self.logger.info(f"Connected to HTTP device: {self.base_url}")
            return True

        except Exception as e:
            self.status = ConnectionStatus.ERROR
            raise ConnectionError(f"Failed to connect: {e}")

    async def disconnect(self) -> bool:
        """Disconnect from HTTP device"""
        if self._client:
            await self._client.aclose()
            self._client = None

        self.status = ConnectionStatus.DISCONNECTED
        self.logger.info("Disconnected from HTTP device")
        return True

    async def read_single(self, address: str) -> DataPoint:
        """
        Read a single data point

        Args:
            address: Endpoint path or configured endpoint name
        """
        if not self._client:
            raise ConnectionError("Not connected")

        try:
            # Resolve address (support shorthand)
            endpoint = self._resolve_endpoint(address)

            # Send GET request
            response = await self._retry_operation(self._client.get, endpoint)

            # Check status code
            response.raise_for_status()

            # Parse response
            data = response.json()

            # Extract value (support multiple formats)
            value = self._extract_value(data)
            quality = self._extract_quality(data, response.status_code)

            return DataPoint(
                value=value, timestamp=datetime.utcnow(), quality=quality, address=address, metadata={"raw_response": data}
            )

        except httpx.HTTPStatusError as e:
            raise ReadError(f"HTTP error {e.response.status_code}: {e}")
        except Exception as e:
            raise ReadError(f"Failed to read {address}: {e}")

    async def read_multiple(self, addresses: list[str]) -> list[DataPoint]:
        """Batch read (concurrent requests)"""
        import asyncio

        tasks = [self.read_single(addr) for addr in addresses]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Handle exceptions
        data_points: list[DataPoint] = []
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                # Read failed, return error data point
                data_points.append(
                    DataPoint(
                        value=None,
                        timestamp=datetime.utcnow(),
                        quality=DataQuality.BAD,
                        address=addresses[i],
                        metadata={"error": str(result)},
                    )
                )
            else:
                data_points.append(result)

        return data_points

    async def write_single(self, address: str, value: Any) -> bool:
        """
        Write a single data point (POST/PUT request)

        Configuration example:
        {
            "write_method": "POST",  # POST / PUT / PATCH
            "write_format": "json"   # json / form / query
        }
        """
        if not self._client:
            raise ConnectionError("Not connected")

        try:
            endpoint = self._resolve_endpoint(address)
            method = self.config.get("write_method", "POST").upper()
            format_type = self.config.get("write_format", "json")

            # Build request
            kwargs: dict[str, Any] = {}
            if format_type == "json":
                payload = {"value": value}
                kwargs = {"json": payload}
            elif format_type == "form":
                kwargs = {"data": {"value": value}}
            else:  # query
                kwargs = {"params": {"value": value}}

            # Send request
            if method == "POST":
                response = await self._retry_operation(self._client.post, endpoint, **kwargs)
            elif method == "PUT":
                response = await self._retry_operation(self._client.put, endpoint, **kwargs)
            else:
                raise WriteError(f"Unsupported method: {method}")

            response.raise_for_status()
            return True

        except Exception as e:
            raise WriteError(f"Failed to write {address}: {e}")

    # ===== Helper methods =====

    def _build_auth_headers(self) -> dict[str, str]:
        """Build authentication headers"""
        headers: dict[str, str] = {}

        auth_type = self.auth.get("type", "none")

        if auth_type == "bearer":
            token = self.auth.get("token")
            if token:
                headers["Authorization"] = f"Bearer {token}"

        elif auth_type == "basic":
            username = self.auth.get("username")
            password = self.auth.get("password")
            if username and password:
                credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
                headers["Authorization"] = f"Basic {credentials}"

        elif auth_type == "api_key":
            key_name = self.auth.get("key_name", "X-API-Key")
            key_value = self.auth.get("key_value")
            if key_value:
                headers[key_name] = key_value

        return headers

    def _resolve_endpoint(self, address: str) -> str:
        """Resolve endpoint address"""
        # If full path, return directly
        if address.startswith(("/", "http")):
            return address

        # Lookup configured endpoint
        if address in self.endpoints:
            return self.endpoints[address]

        # Default: append to base_url
        return f"/{address}"

    def _extract_value(self, data: Any) -> Any:
        """Extract value from response"""
        # If simple value, return directly
        if not isinstance(data, dict):
            return data

        if self.value_path:
            current: Any = data
            for key in self.value_path:
                if isinstance(current, dict) and key in current:
                    current = current[key]
                else:
                    current = None
                    break
            if current is not None:
                if isinstance(current, str):
                    try:
                        return float(current)
                    except ValueError:
                        return current
                if isinstance(current, int | float):
                    return current
                return current

        # Try common value fields
        for key in ["value", "data", "result", "reading"]:
            if key in data:
                return data[key]

        # Return entire object
        return data

    def _extract_quality(self, data: Any, status_code: int) -> DataQuality:
        """Infer data quality"""
        if status_code >= 500:
            return DataQuality.BAD

        if isinstance(data, dict):
            quality_str = data.get("quality", "good").lower()
            if quality_str in ["good", "ok", "normal"]:
                return DataQuality.GOOD
            elif quality_str in ["bad", "error", "fail"]:
                return DataQuality.BAD
            elif quality_str in ["uncertain", "warning"]:
                return DataQuality.UNCERTAIN

        return DataQuality.GOOD
